using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Runtime.CompilerServices;
using InfiniTec.Security.Privileges;

namespace InfiniTec.Security
{
    /// <summary>
    /// This class allows the manipulation of the privileges held by a logon token.
    /// </summary>
    public class Privilege
    {
        /// <summary>
        /// Required to assign the primary token of a process.
        /// </summary>
        public const string AssignPrimaryToken = "SeAssignPrimaryTokenPrivilege";
        /// <summary>
        /// Required to generate audit-log entries.
        /// </summary>
        public const string Audit = "SeAuditPrivilege";
        /// <summary>
        /// Required to perform backup operations.
        /// </summary>
        public const string Backup = "SeBackupPrivilege";
        /// <summary>
        /// Required to receive notifications of changes to files or directories. 
        /// This privilege also causes the system to skip all traversal access checks. 
        /// It is enabled by default for all users.
        /// </summary>
        public const string ChangeNotify = "SeChangeNotifyPrivilege";
        /// <summary>
        /// Create global objects.
        /// </summary>
        public const string CreateGlobal = "SeCreateGlobalPrivilege";
        /// <summary>
        /// Required to create a paging file.
        /// </summary>
        public const string CreatePageFile = "SeCreatePagefilePrivilege";
        /// <summary>
        /// Required to create a permanent object.
        /// </summary>
        public const string CreatePermanent = "SeCreatePermanentPrivilege";
        /// <summary>
        /// Required to create a primary token.
        /// </summary>
        public const string CreateToken = "SeCreateTokenPrivilege";
        /// <summary>
        /// Required to debug a process.
        /// </summary>
        public const string Debug = "SeDebugPrivilege";
        /// <summary>
        /// Required to enable computer and user accounts to be trusted for delegation.
        /// </summary>
        public const string EnableDelegation = "SeEnableDelegationPrivilege";
        /// <summary>
        /// Impersonate a client after authentication.
        /// </summary>
        public const string Impersonate = "SeImpersonatePrivilege";
        /// <summary>
        /// Required to increase the base priority of a process.
        /// </summary>
        public const string IncreaseBasePriority = "SeIncreaseBasePriorityPrivilege";
        /// <summary>
        /// Required to increase the quota assigned to a process.
        /// </summary>
        public const string IncreaseQuota = "SeIncreaseQuotaPrivilege";
        /// <summary>
        /// Required to load or unload a device driver.
        /// </summary>
        public const string LoadDriver = "SeLoadDriverPrivilege";
        /// <summary>
        /// Required to lock physical pages in memory.
        /// </summary>
        public const string LockMemory = "SeLockMemoryPrivilege";
        /// <summary>
        /// Required to create a computer account.
        /// </summary>
        public const string MachineAccount = "SeMachineAccountPrivilege";
        /// <summary>
        /// The SeManageVolumePrivilege will let nonadministrators and remote 
        /// users do administrative disk tasks on a machine.
        /// </summary>
        public const string ManageVolume = "SeManageVolumePrivilege";
        /// <summary>
        /// Required to gather profiling information for one process.
        /// </summary>
        public const string ProfileSingleProcess = "SeProfileSingleProcessPrivilege";
        /// <summary>
        /// Required to shut down a system using a network request.
        /// </summary>
        public const string RemoteShutdown = "SeRemoteShutdownPrivilege";
        /// <summary>
        /// The meaning of this privilege is currenty unknown.
        /// </summary>
        public const string ReserveProcessor = "SeReserveProcessorPrivilege";
        /// <summary>
        /// Required to perform restore operations. This privilege enables you to set 
        /// any valid user or group security identifier (SID) as the owner of an object.
        /// </summary>
        public const string Restore = "SeRestorePrivilege";
        /// <summary>
        /// Required to perform a number of security-related functions, 
        /// such as controlling and viewing audit messages. This privilege 
        /// identifies its holder as a security operator.
        /// </summary>
        public const string Security = "SeSecurityPrivilege";
        /// <summary>
        /// Required to shut down a local system.
        /// </summary>
        public const string Shutdown = "SeShutdownPrivilege";
        /// <summary>
        /// Required to synchronize directory service data.
        /// </summary>
        public const string SyncAgent = "SeSyncAgentPrivilege";
        /// <summary>
        /// Required to modify the nonvolatile RAM of systems that use this type of 
        /// memory to store configuration information.
        /// </summary>
        public const string SystemEnvironment = "SeSystemEnvironmentPrivilege";
        /// <summary>
        /// Required to gather profiling information for the entire system.
        /// </summary>
        public const string SystemProfile = "SeSystemProfilePrivilege";
        /// <summary>
        /// Required to modify the system time.
        /// </summary>
        public const string SystemTime = "SeSystemtimePrivilege";
        /// <summary>
        /// Required to take ownership of an object without being granted discretionary access. 
        /// This privilege allows the owner value to be set only to those values that the holder 
        /// may legitimately assign as the owner of an object.
        /// </summary>
        public const string TakeOwnership = "SeTakeOwnershipPrivilege";
        /// <summary>
        /// Identifies its holder as part of the trusted computer base. Some trusted, 
        /// protected subsystems are granted this privilege.
        /// </summary>
        public const string TrustedComputingBase = "SeTcbPrivilege";
        /// <summary>
        /// The meaning of this privilige is currently unknown.
        /// </summary>
        public const string TrustedCredentialManagerAccess = "SeTrustedCredManAccessPrivilege";
        /// <summary>
        /// Required to remove a computer from a docking station.
        /// </summary>
        public const string Undock = "SeUndockPrivilege";
        /// <summary>
        /// The meaning of this privilige is currently unknown.
        /// </summary>
        public const string UnsolicitedInput = "SeUnsolicitedInputPrivilege";

        private string _Name;

        /// <summary>
        /// Returns the name of the privilege, e.g. SeRemoteShutdownPrivilege.
        /// </summary>
        public string Name
        {
            get { return _Name; }
        }

        private LUID _Luid;

        internal LUID Luid
        {
            get { return _Luid; }
        }

        private PrivilegeState _State;
        private WindowsIdentity _Identity;
        private PrivilegeCollection _Privileges;

        internal PrivilegeCollection Privileges
        {
            get { return _Privileges; }
        }


        internal Privilege(PrivilegeCollection privileges, WindowsIdentity identity, LUID luid, PrivilegeState state)
        {
            _Luid = luid;
            _Privileges = privileges;
            _Identity = identity;
            _Name = LookupPrivilegeName();
            _State = state;
        }

        private string LookupPrivilegeName()
        {
            StringBuilder name;
            uint length;
            bool result;

            name = new StringBuilder(256);
            length = (uint) name.Capacity;

            result = NativeMethods.LookupPrivilegeName(null, ref _Luid, name, ref length);

            if (!result) throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());

            return name.ToString();
        }

        private string _DisplayName;

        /// <summary>
        /// Returns the display name of the current privilege.
        /// </summary>
        public string DisplayName
        {
            get
            {
                if (string.IsNullOrEmpty(_DisplayName)) _DisplayName = LookupPrivilegeDisplayName();
                return _DisplayName;
            }
        }

        private string LookupPrivilegeDisplayName()
        {
            StringBuilder sb;
            uint length;
            uint languageId;
            bool result;

            sb = new StringBuilder(256);
            length = (uint) sb.Capacity;

            result = NativeMethods.LookupPrivilegeDisplayName(null, Name, sb, ref length, out languageId);
            if (!result)
            {
                if (length > sb.Capacity)
                {
                    length++;
                    sb.Capacity = (int) length + 1;
                    result = NativeMethods.LookupPrivilegeDisplayName(null, Name, sb, ref length, out languageId);
                }
            }
            if (!result)
            {
                throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
            }
            return sb.ToString();
        }

        /// <summary>
        /// Returns the <see cref="PrivilegeState"/> of the Privilege represented by this instance.
        /// </summary>
        public PrivilegeState State
        {
            get
            {
                return _State;
            }
        }

        /// <summary>
        /// Enables the privilige represented by the current instance.
        /// </summary>
        public void Enable()
        {
            AdjustTokenPrivileges(_Identity, false, new PrivilegeAction(_Luid, PrivilegeState.Enable));
            _State = PrivilegeState.Enable;
        }

        internal static void AdjustTokenPrivileges(WindowsIdentity identity,
            bool disableAllPrivileges, params PrivilegeAction[] privilegesToModify)
        {
            TOKEN_PRIVILEGES newState;
            bool result;
            uint returnLength = 0;

            newState = new TOKEN_PRIVILEGES();
            newState.PrivilegeCount = (uint)privilegesToModify.Length;
            newState.Privileges = new LUID_AND_ATTRIBUTES[privilegesToModify.Length];

            for (int i = 0; i < privilegesToModify.Length; i++)
            {
                newState.Privileges[i] = new LUID_AND_ATTRIBUTES();
                newState.Privileges[i].Attributes = (uint)privilegesToModify[i].State;
                newState.Privileges[i].Luid = privilegesToModify[i].Privilege;
            }


            using (SafeTokenPrivileges handle = new SafeTokenPrivileges(newState))
            {
                result = NativeMethods.AdjustTokenPrivileges(identity.Token, disableAllPrivileges,
                    handle, handle.Size, IntPtr.Zero, ref returnLength);
                if (!result) throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
            }
        }

        /// <summary>
        /// Disabled the privilige represented by the current instance.
        /// </summary>
        public void Disable()
        {
            AdjustTokenPrivileges(_Identity, false, new PrivilegeAction(_Luid, PrivilegeState.Disable));
            _State = PrivilegeState.Disable;
        }

        /// <summary>
        /// Removes the privilege represented by this instance. Once a privilige is removed
        /// from a token, it cannot be added back.
        /// </summary>
        public void Remove()
        {
            AdjustTokenPrivileges(_Identity, false, new PrivilegeAction(_Luid, PrivilegeState.Remove));
            _State = PrivilegeState.Remove;
            Privileges.RemovePrivilege(Name);
        }

        /// <summary>
        /// Returns the <see cref="Name"/> of the privilege.
        /// </summary>
        /// <returns>The name of the privilege represented by this instance.</returns>
        public override string ToString()
        {
            return Name;
        }
    }
}
